package ink.mint.neural;

import ink.mint.activation.Activateable;
import ink.mint.activation.ActivationFunction;
import ink.mint.activation.ActivationType;
import ink.mint.loss.LossType;
import ink.mint.matrix.Matrix;
import ink.mint.matrix.Matrixable;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class Layer implements Serializable {
    /**
     * 网络层的神经元个数
     */
    private int neuralNumber;
    /**
     * 输入向量
     */
    private double[] input;
    /**
     * 权重矩阵
     */
    private double[][] weight;
    /**
     * 偏置向量
     */
    private double[] bias;
    /**
     * z计算的输出值向量
     */
    private double[] zLayer;
    /**
     * 神经元激活后的向量值
     */
    private double[] activateLayer;

    /**
     * 权重更新的误差矩阵
     */
    private double[][] deltaWeight;
    /**
     * 偏置更新的误差向量
     */
    private double[] deltaBias;
    /**
     * 矩阵类接口
     */
    private Matrixable matrix;
    /**
     * 激活函数的类型
     */
    private ActivationType activationType;
    /**
     * 激活函数的接口
     */
    private Activateable activateable;

    /**
     * 网络层构造函数
     */
    public Layer() {
        this.activateable = new ActivationFunction();
        this.matrix = new Matrix();
    }

    /**
     * 网络层构造函数
     *
     * @param neuralNumber   网络层神经元个数
     * @param activationType 网络层激活函数类型
     */
    public Layer(int neuralNumber, ActivationType activationType) {
        this.neuralNumber = neuralNumber;
        this.activationType = activationType;
        this.activateable = new ActivationFunction();
        this.matrix = new Matrix();
    }

    /**
     * 网络层输入函数
     *
     * @param input   输入向量
     * @param isTrain 是否训练还是运行测试
     * @return 网络层
     */
    public Layer input(double[] input, boolean isTrain) {
        this.input = input;
        if (weight == null || bias == null) {
            initLayer();
        } else {
            if (isTrain) {
                runLayer(matrix.sub(weight, deltaWeight), matrix.sub(bias, deltaBias));
            } else {
                runLayer(weight, bias);
            }

        }
        return this;
    }

    /**
     * 网络层输入函数
     *
     * @param input  输入向量
     * @param weight 权重矩阵
     * @param bias   偏置向量
     * @return 网络层
     */
    public Layer input(double[] input, double[][] weight, double[] bias) {
        this.input = input;
        if (weight == null || bias == null) {
            initLayer();
        } else {
            runLayer(weight, bias);
        }
        return this;
    }

    /**
     * 构建网络层
     *
     * @param neuralNumber   网络层神经元个数
     * @param activationType 网络层激活函数类型
     * @return 网络层
     */
    public Layer createLayer(int neuralNumber, ActivationType activationType) {
        if (neuralNumber != 0) {
            this.neuralNumber = neuralNumber;
            this.activationType = activationType;
            return this;
        } else {
            System.out.println("构造网络层数据错误不能为0");
            return this;
        }
    }

    /**
     * 随机初始化网络层
     */
    private void initLayer() {
        int ir = input.length;
        this.weight = matrix.random(neuralNumber, ir);
        this.bias = matrix.random(neuralNumber);
    }

    /**
     * 运行网络层 设置权重和偏置
     *
     * @param weight 权重矩阵
     * @param bias   偏置向量
     */
    private void runLayer(double[][] weight, double[] bias) {
        setWeight(weight);
        setBias(bias);
    }

    /**
     * 网络层z计算函数
     *
     * @return z向量
     */
    private double[] z() {
        double[][] inputMatrix = matrix.vectorToColumnMatrix(input);
        double[][] product = matrix.multi(weight, inputMatrix);
        double[] productVecotr = matrix.matrixToVector(product);
        double[] result = matrix.add(productVecotr, bias);
        this.zLayer = result;
        return result;
    }

    /**
     * 网络层激活函数
     *
     * @param activationType 激活函数类别
     * @return 激活向量
     */
    public double[] activate(ActivationType activationType) {
        double[] result = activateable.ActivationSolve(z(), activationType);
        this.activateLayer = result;
        return result;
    }

    /**
     * 获取网络层神经元个数
     *
     * @return 神经元个数
     */
    public int getNeuralNumber() {
        return neuralNumber;
    }

    /**
     * 设置网络层神经元个数
     *
     * @param neuralNumber 神经元个数
     */
    public void setNeuralNumber(int neuralNumber) {
        this.neuralNumber = neuralNumber;
    }

    /**
     * 获取输入向量
     *
     * @return 输入向量
     */
    public double[] getInput() {
        return input;
    }

    /**
     * 设置输入向量
     *
     * @param input 输入向量
     */
    public void setInput(double[] input) {
        this.input = input;
    }

    /**
     * 获取矩阵接口
     *
     * @return 矩阵接口
     */
    public Matrixable getMatrix() {
        return matrix;
    }

    /**
     * 设置矩阵接口
     *
     * @param matrix 矩阵接口
     */
    public void setMatrix(Matrixable matrix) {
        this.matrix = matrix;
    }

    /**
     * 获取激活函数接口
     *
     * @return 激活函数接口
     */
    public Activateable getActivateable() {
        return activateable;
    }

    /**
     * 设置激活函数接口
     *
     * @param activateable 激活函数接口
     */
    public void setActivateable(Activateable activateable) {
        this.activateable = activateable;
    }

    /**
     * 获取权重矩阵
     *
     * @return 权重矩阵
     */
    public double[][] getWeight() {
        return weight;
    }

    /**
     * 设置权重矩阵
     *
     * @param weight 权重矩阵
     */
    public void setWeight(double[][] weight) {
        this.weight = weight;
    }

    /**
     * 获取偏置向量
     *
     * @return 偏置向量
     */
    public double[] getBias() {
        return bias;
    }

    /**
     * 设置偏置向量
     *
     * @param bias 偏置向量
     */
    public void setBias(double[] bias) {
        this.bias = bias;
    }

    /**
     * 获取z计算网络层向量
     *
     * @return z计算网络层向量
     */
    public double[] getzLayer() {
        return zLayer;
    }

    /**
     * 设置z计算网络层向量
     *
     * @param zLayer z计算网络层向量
     */
    public void setzLayer(double[] zLayer) {
        this.zLayer = zLayer;
    }

    /**
     * 获取网络层激活向量
     *
     * @return 网络层激活向量
     */
    public double[] getActivateLayer() {
        return activateLayer;
    }

    /**
     * 设置网络层激活向量
     *
     * @param activateLayer 网络层激活向量
     */
    public void setActivateLayer(double[] activateLayer) {
        this.activateLayer = activateLayer;
    }

    /**
     * 获取网络层激活函数类型
     *
     * @return 网络层激活函数类型
     */
    public ActivationType getActivationType() {
        return activationType;
    }

    /**
     * 设置网络层激活函数类型
     *
     * @param activationType 网络层激活函数类型
     */
    public void setActivationType(ActivationType activationType) {
        this.activationType = activationType;
    }

    /**
     * 获取权重更新误差矩阵
     *
     * @return 权重更新误差矩阵
     */
    public double[][] getDeltaWeight() {
        return deltaWeight;
    }

    /**
     * 设置权重更新误差矩阵
     *
     * @param deltaWeight 权重更新误差矩阵
     */
    public void setDeltaWeight(double[][] deltaWeight) {
        this.deltaWeight = deltaWeight;
    }

    /**
     * 获取偏置更新误差向量
     *
     * @return 偏置更新误差向量
     */
    public double[] getDeltaBias() {
        return deltaBias;
    }

    /**
     * 设置偏置更新误差向量
     *
     * @param deltaBias 偏置更新误差向量
     */
    public void setDeltaBias(double[] deltaBias) {
        this.deltaBias = deltaBias;
    }

}
